package com.symaster.common.geom.algorithm;

import com.symaster.common.geom.entity.Node;
import com.symaster.common.geom.entity.Point;
import com.symaster.common.geom.entity.Polygon;
import com.symaster.common.geom.entity.Vector;
import com.symaster.common.geom.util.PointUtil;
import com.symaster.common.util.ArrayUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author yinmiao
 * @date 2022-01-17 14:17
 */
public class PolygonPanel {

    private static final Logger logger = LoggerFactory.getLogger(PolygonPanel.class);
    private final double width;  // 排样宽度
    private final double height; // 排样高度
    private final double marge; // 边距
    private final Map<Integer, Node> idToNodeMap = new HashMap<>();
    private final List<Node> nodes = new LinkedList<>();
    private final double acceptableHeightDifference;
    private Double useHeight;
    private String gene;
    private String otherConfiguration;
    private String properties;
    private Boolean error;
    private Double areaTotalSize;

    public String getProperties() {
        return properties;
    }

    public void setProperties(String properties) {
        this.properties = properties;
    }

    public PolygonPanel(double width, double height, double marge) {
        this.acceptableHeightDifference = width * 0.01;
        this.width = width;
        this.height = height;
        this.marge = marge;
    }

    public void add(Node node) {
        addToIndexMap(node); // 添加索引映射

        // if (error == null || !error) error = isError(node);
        initMovePoint(node.getId()); // 初始化图形坐标
        if (error == null || !error) {
            autoLayout(node.getId()); // 自动对齐
        }
        updateUseHeight();
        if (error == null || !error) {
            error = isError(node);
        }
    }

    private void updateAreaTotalSize() {
        if (nodes.size() == 0) {
            return;
        }

        areaTotalSize = 0.0;
        for (Node node : nodes) {
            areaTotalSize += node.getPolygon().getArea();
        }
    }

    private boolean isError(Node node) {
        if (node.getWidth() > width) {
            logger.error("node.getWidth() > width");
            return true;
        }
        if (node.getHeight() > height) {
            logger.error("node.getHeight() > height");
            return true;
        }
        if (node.getX() + node.getWidth() > width) {
            logger.error("node.getX() + node.getWidth() > width");
            return true;
        }
        return false;
    }

    public void add(Node node, boolean resetCoordinates) {
        addToIndexMap(node); // 添加索引映射
        if (resetCoordinates) {
            node.move(0, 0);
        }
        updateUseHeight();
    }

    public double getFitness() {
        if (error != null && error) {
            return -(useHeight + 100);
        }
        return height - useHeight;
    }

    public Polygon getMinArea(boolean isSwellPolygon) {
        LinkedList<Point> points = new LinkedList<>();
        for (Node node : nodes) {
            points.addAll(PointUtil.polygon2points(node.getPolygon()));
        }
        return PointUtil.convexHull(PointUtil.points2polygon(points));
    }

    public double getFitnessByArea(boolean isSwellPolygon) {
        if (error != null && error) {
            return -(getMinArea(isSwellPolygon).getArea() + 100);
        }
        updateAreaTotalSize();
        return areaTotalSize * (marge * 0.1) - getMinArea(isSwellPolygon).getArea();
    }

    /**
     * 初始化图形坐标
     */
    public void initMovePoint(Integer id) {
        Node node = fontNodeById(id);
        if (useHeight == null || useHeight <= 0) {
            node.move(0, 0);
            useHeight = node.getHeight();
            return;
        }

        double dstX = width - node.getWidth() - 1;
        double dstY = useHeight + marge;
        node.move(dstX, dstY);
        int count = 0;
        while (moveOver(id)) {
            dstY = dstY + marge;
            node.move(dstX, dstY);
            count++;
            if (count > 1000) {
                throw new IllegalArgumentException();
            }
        }
    }

    /**
     * 自动对齐
     * 时间消耗最大, 找到位置最好
     */
    public void autoLayout(Integer id) {

        Node node = fontNodeById(id);
        if (nodes.size() <= 1) {
            node.move(0, 0);
            return;
        }

        double offset = Math.max(marge * 0.5, 0.5);
        Point minPoint = new Point(node.getX(), node.getY());
        while (node.getX() >= 0) {
            double appropriateY = getAppropriateY(node);
            moveY(node, appropriateY);
            minPoint = minPoint(new Point(node.getX(), node.getY()), minPoint);
            while (node.getY() >= 0) {
                if (translateY(node, -offset)) {
                    minPoint = minPoint(new Point(node.getX(), node.getY()), minPoint);
                } else {
                    break;
                }
            }
            moveX(node, node.getX() - offset);
        }
        node.move(minPoint);
    }

    private double getAppropriateY(Node node) {
        List<Node> nodes = fontNodesByRectangleArea((node.getX() - marge), 0, (node.getWidth() + (marge * 2)), ((node.getY() + node.getHeight()) + (marge * 2)));
        double max = 0.0D;
        for (Node node1 : nodes) {
            if (node1 == node) {
                continue;
            }
            max = Math.max(node1.getY() + node1.getHeight(), max);
        }
        return max + marge;
    }

    public Polygon composeByConvexHull() {
        if (nodes.size() == 0) {
            return null;
        }
        if (nodes.size() == 1) {
            return nodes.get(0).getPolygon();
        }

        double[] xpoints = null;
        double[] ypoints = null;

        for (Node node : nodes) {
            Polygon polygon = node.getPolygon();
            xpoints = ArrayUtil.arrayAppend(xpoints, polygon.getXpoints());
            ypoints = ArrayUtil.arrayAppend(ypoints, polygon.getYpoints());
        }

        return PointUtil.convexHull(new Polygon(xpoints, ypoints));
    }

    /**
     * 自动对齐
     */
    public void autoLayout2(Integer id) {

        Node node = fontNodeById(id);
        if (nodes.size() <= 1) {
            node.move(0, 0);
            return;
        }

        double minX = node.getWidth();
        double minY = node.getHeight();
        boolean yMove = false;
        int count = 0;

        while (true) {
            count++;
            double x = node.getX();
            double y = node.getY();
            if ((x * 0.4 + y * 0.6) <= (minX * 0.4 + minY * 0.6)) {
                minX = x;
                minY = y;
            }
            if (!yMove && translateY(id, -1)) {
                continue;
            }
            if (translateX(id, -1)) {
                continue;
            }
            if (!yMove && translateY(id, +10)) {
                yMove = true;
            }
            if (translateX(id, -1)) {
                continue;
            }
            node.move(minX, minY);
            break;
        }
    }

    /**
     * 自动对齐
     */
    public void autoLayout3(Integer id) {

        Node node = fontNodeById(id);
        if (nodes.size() <= 1) {
            node.move(0, 0);
            return;
        }

        Point tempPoint2 = new Point(node.getX(), node.getY());
        translateToUpper(node);
        Point minPoint = new Point(node.getX(), node.getY());
        node.move(tempPoint2);

        while (translateX(node, -1)) {
            Point tempPoint = new Point(node.getX(), node.getY());
            translateToUpper(node);
            minPoint = minPoint(new Point(node.getX(), node.getY()), minPoint);
            node.move(tempPoint);
        }
        node.move(minPoint);
    }

    public void autoLayout4(Integer id) {

        Node node = fontNodeById(id);
        if (nodes.size() <= 1) {
            node.move(0, 0);
            return;
        }

        translateToUpper(node);
        translateToLeft(node);
    }

    /**
     * 判断图形是否重叠以及是否移出画布
     *
     * @return true: 重叠或已经移出画布 | false: 未重叠并且未移出画布
     */
    public boolean moveOver(Integer id) {

        Node node = fontNodeById(id);

        /* 判断是否移出画布 */
        if (graphicsOutOfBounds(node)) {
            return true;
        }

        /* 测试是否与其他图形重叠 */
        double marge = this.marge;
        List<Node> nodes = fontNodesByRectangleArea((node.getX() - marge), (node.getY() - marge), (node.getWidth() + (marge * 2)), (node.getHeight() + (marge * 2)));

        for (Node nodeItem : nodes) {
            if (nodeItem.getId().equals(id)) {
                continue;
            }
            if (node.getSwellPolygon().collide(nodeItem.getPolygon())) {
                return true;
            }
        }

        return false;
    }

    public boolean moveOverNotMarge(Integer id) {

        Node node = fontNodeById(id);

        /* 判断是否移出画布 */
        if (graphicsOutOfBounds(node)) {
            return true;
        }

        /* 测试是否与其他图形重叠 */
        double marge = this.marge;
        List<Node> nodes = fontNodesByRectangleArea((node.getX() - marge), (node.getY() - marge), (node.getWidth() + (marge * 2)), (node.getHeight() + (marge * 2)));

        for (Node nodeItem : nodes) {
            if (nodeItem.getId().equals(id)) {
                continue;
            }
            if (node.getPolygon().collide(nodeItem.getPolygon())) {
                return true;
            }
        }

        return false;
    }

    /**
     * 通过id查询node
     */
    public Node fontNodeById(Integer id) {
        return idToNodeMap.get(id);
    }

    /**
     * 通过矩形区域查询nodes
     */
    public List<Node> fontNodesByRectangleArea(double x, double y, double width, double height) {
        return this.nodes.stream().filter(e -> {
            if (e.getX() > x + width) {
                return false;
            }
            if (e.getY() > y + height) {
                return false;
            }
            if (e.getX() + e.getWidth() < x) {
                return false;
            }
            return !(e.getY() + e.getHeight() < y);
        }).collect(Collectors.toList());
    }

    public String getGene() {
        return gene;
    }

    public void setGene(String gene) {
        this.gene = gene;
    }

    public String getOtherConfiguration() {
        return otherConfiguration;
    }

    public void setOtherConfiguration(String otherConfiguration) {
        this.otherConfiguration = otherConfiguration;
    }

    public double getWidth() {
        return width;
    }

    public double getHeight() {
        return height;
    }

    public double getMarge() {
        return marge;
    }

    public Map<Integer, Node> getIdToNodeMap() {
        return idToNodeMap;
    }

    public List<Node> getNodes() {
        return nodes;
    }

    public Double getUseHeight() {
        return useHeight;
    }

    private void updateUseHeight() {
        if (nodes.size() - 1 < 0) {
            this.useHeight = 0.0D;
            return;
        }
        if (nodes.size() - 1 < 1) {
            this.useHeight = nodes.get(0).getHeight();
            return;
        }
        double maxUseHeight = 0;
        for (Node node : nodes) {
            double useY = node.getY() + node.getHeight();
            if (useY > maxUseHeight) {
                maxUseHeight = useY;
            }
        }
        this.useHeight = maxUseHeight;
    }

    private Point minPoint(Point p1, Point p2) {
        if (p1.getY() < p2.getY()) {
            return p1;
        }

        if (Math.abs(p1.getY() - p2.getY()) <= 0.001 && p1.getX() < p2.getX()) {
            return p1;
        }
        return p2;
    }

    private Point minPoint2(Point p1, Point p2) {

        double v = width / height;
        double v1 = height / width;

        if (((p1.x * v1) + (p1.y * v)) > ((p2.x * v1) + (p2.y * v))) {
            return p1;
        }
        return p2;
    }

    private Point minPoint3(Point p1, Point p2) {
        return (p1.y < p2.y) ? p1 : p2;
    }

    private int translateToUpper(Node node) {
        int count = 0;
        while (translateY(node, -1)) {
            count++;
        }
        return count;
    }

    private double translateToRight(Node node) {
        int count = 0;
        while (translateX(node, 1)) {
            count++;
        }
        return count;
    }

    private double translateToLeft(Node node) {
        int count = 0;
        while (translateX(node, -1)) {
            count++;
        }
        return count;
    }

    private boolean translateX(Integer id, double xn) {
        fontNodeById(id).translate(new Vector(xn, 0));
        return !moveOver(id);
    }

    private boolean translateY(Integer id, double yn) {
        fontNodeById(id).translate(new Vector(0, yn));
        return !moveOver(id);
    }

    private boolean translateX(Node node, double xn) {
        node.translate(new Vector(xn, 0));
        return !moveOver(node.getId());
    }

    private boolean translateY(Node node, double yn) {
        node.translate(new Vector(0, yn));
        return !moveOver(node.getId());
    }

    private void moveX(Integer id, double x) {
        Node node = fontNodeById(id);
        double v = x - node.getX();
        node.translate(v, 0);
    }

    private void moveX(Node node, double x) {
        double v = x - node.getX();
        node.translate(v, 0);
    }

    private void moveY(Integer id, double y) {
        Node node = fontNodeById(id);
        double v = y - node.getY();
        node.translate(0, v);
    }

    private void moveY(Node node, double y) {
        double v = y - node.getY();
        node.translate(0, v);
    }

    /**
     * 测试图形是否越界
     */
    private boolean graphicsOutOfBounds(Node node) {
        if (node.getX() < 0 || node.getY() < 0) {
            return true;
        }
        return node.getX() + node.getWidth() > width;
    }

    /**
     * 添加索引映射
     */
    private void addToIndexMap(Node node) {
        if (node.getId() == null) {
            throw new IllegalArgumentException("node必要参数'id'不能为空");
        }
        nodes.add(node);
        idToNodeMap.put(node.getId(), node);
    }

    public void remove(Integer id) {
        Node remove = idToNodeMap.remove(id);
        nodes.remove(remove);
        updateUseHeight();
    }
}
